#ifndef SHADER_H
#define SHADER_H

/*
 * Shader Code Type
 */
typedef enum
{
    VERTEX = GL_VERTEX_SHADER,
    FRAGMENT = GL_FRAGMENT_SHADER
} ShaderType;

/* 
 * Create a new shader program 
 *
 * Parameter
 *      - vertex_shader_code: vertex shader source code
 *      - fragment_shader_code: fragment shader source code
 * 
 * Return
 *      - Shader Program id
*/
unsigned int create_shader_program(const char *vertex_shader_code, const char *fragment_shader_code);

/* Create Shader Program from file */
unsigned int create_shader_program_from_file(const char *vertex_shader_file, const char *fragment_shader_file);

/*
 * Compile Single Shader FILE
 *
 * Parameter:
 *      - shader_code: shader program source code
 *      - type: shader type (VERTEX or FRAGMENT)
 */
unsigned int compile_shader_code(const char *shader_code, ShaderType type);

/* enable shader program */
void enable_shader_program(unsigned int shader_program_id);

/* disable shader program */
void disable_shader_program();

/* 
 * Delete shader program 
 * 
 * Parameter
 *      - shader_program_id: The shader program id that you want to delete
 */
void delete_shader_program(unsigned int shader_program_id);

/* Load File Text */
char *load_file_text(const char *file_name);

/* Free File Text */
void free_file_text(char *text);

#endif /* END SHADER_H */

#ifdef SHADER_IMPLEMENTATION

#include <stdio.h>
#include <stdlib.h>

#define enable_shader_program(shader_program_id) \
    glUseProgram(shader_program_id);             \

#define disable_shader_program() glUseProgram(0);


unsigned int create_shader_program_from_file(const char *vertex_shader_file, const char *fragment_shader_file)
{
    unsigned int shader_proagem_id = 0;

    char *vs = load_file_text(vertex_shader_file);
    char *fs = load_file_text(fragment_shader_file);
    
    shader_proagem_id = create_shader_program(vs, fs);

    free_file_text(vs);
    free_file_text(fs);

    return shader_proagem_id;
}

unsigned int compile_shader_code(const char *shader_code, ShaderType type)
{
    unsigned int shader = 0;
    shader = glCreateShader(type);

    const char *shader_type = NULL;
    if (type == VERTEX)
    {
        shader_type = "vertex";
    }else{
        shader_type = "fragment";
    }
    

    glShaderSource(shader, 1, &shader_code, NULL);

    int success = 0;
    glCompileShader(shader);
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);

    if (success == GL_FALSE)
    {
        fprintf(stderr, "failed to compile shader code\n");

        int max_length = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &max_length);

        if (max_length > 0)
        {
            int length = 0;
            char *log = (char *)calloc(max_length, sizeof(char));
            glGetShaderInfoLog(shader, max_length, &length, log);
            fprintf(stderr, "%s shader id:%i compile error: %s\n", shader_type, shader, log);
            free(log);
        }
        return 0;
    }

    return shader;
}

unsigned int create_shader_program(const char *vertex_shader_code, const char *fragment_shader_code)
{
    unsigned int shader_proagem_id = 0;

    unsigned int vertex_shader_id = 0;
    unsigned int fragment_shader_id = 0;

    if (vertex_shader_code == NULL || fragment_shader_code == NULL)
    {
        fprintf(stderr, "Cannot input NULL shader code\n");
        return 0;
    }
    
    vertex_shader_id = compile_shader_code(vertex_shader_code, VERTEX);
    fragment_shader_id = compile_shader_code(fragment_shader_code, FRAGMENT);

    if (vertex_shader_id == 0 || fragment_shader_id == 0)
    {
        return 0;
    }
    
    shader_proagem_id = glCreateProgram();
    glAttachShader(shader_proagem_id, vertex_shader_id);
    glAttachShader(shader_proagem_id, fragment_shader_id);
    glLinkProgram(shader_proagem_id);

    glDeleteShader(vertex_shader_id);
    glDeleteShader(fragment_shader_id);

    return shader_proagem_id;
}

char *load_file_text(const char *file_name)
{
    char *text = NULL;
    FILE *file = fopen(file_name, "rt");
    if (file != NULL)
    {
        fseek(file, 0, SEEK_END);
        unsigned int size = (unsigned int)ftell(file);
        fseek(file, 0, SEEK_SET);
        if (size > 0)
        {
            text = (char *)malloc((size + 1)*sizeof(char));
            if (text != NULL)
            {
                unsigned int count = (unsigned int)fread(text, sizeof(char), size, file);
                if (count < size) 
                {
                    text = (char*)realloc(text, count + 1);
                }
                text[count] = '\0';
            }else 
            {
                fprintf(stderr, "file: %s failed to allocated memory for file reading\n", file_name);
            }
        }else 
        {
            fprintf(stderr, "file: %s failed to read text file\n", file_name);
        }
        fclose(file);
    }else
    {
        fprintf(stderr, "file: %s failed to open text file\n", file_name);
    }

    return text;
}

void free_file_text(char *text)
{
    free(text);
}

#endif